home *** CD-ROM | disk | FTP | other *** search
/ Delphi Developer's Kit 1996 / Delphi Developer's Kit 1996.iso / power / sortlist / tsortlst.pas < prev   
Pascal/Delphi Source File  |  1995-12-22  |  9KB  |  299 lines

  1. unit TSortLst;
  2.  
  3. (*
  4.     Version 1.0 5/12/95 - Mike Stortz
  5.  
  6.       Borland Pascal 7.0 has a very useful object called a TCollection that
  7.     allows you to sort items by any key you wish and that disposes
  8.     of any objects it owns when the collection itself is freed.
  9.     Inexplicably, these very useful behaviors were omitted from Delphi's
  10.     TStringList.  But now... <trumpet fanfare>
  11.  
  12.       This unit contains a class called "TSortableList" derived from
  13.     TStringList that supports the ability for you to define descendent classes
  14.     that sort the list any way you wish and has the option to "own" the objects
  15.     that are added to it, so that they are disposed with the list.
  16.  
  17.     How to use a TSortableList that owns its objects:
  18.  
  19.     1. Create a new TSortableList and tell it that it should own any objects
  20.        in it.
  21.  
  22.       var
  23.         my_list : TSortableList;
  24.       begin
  25.       my_list := TSortableList.Create(True);     { "True" = Owns objects }
  26.       my_list.Add('Aaa', TObject.Create);        { In a TStringList, this... }
  27.       my_list.Add('Bbb', TObject.Create);        { ...would be... }
  28.       my_list.Add('Ccc', TObject.Create);        { ...very bad! }
  29.       my_list.Free;                              { All objects freed }
  30.       end;
  31.  
  32.     How to sort on an arbitrary key:
  33.  
  34.     Suppose you wanted a list of strings sorted by all but the first character
  35.     (i.e. this order -> "ZAble", "YBaker", "XCharlie").
  36.  
  37.     1. Declare a descendent of TSortableList and override the Compare method.
  38.        The Compare method should return an integer such that the result is:
  39.          -1 if the item at index i1 is "less" than the item at i2
  40.           0 if the item at index i1 is "equal" than the item at i2
  41.           1 if the item at index i1 is "more" than the item at i2
  42.  
  43.       TExList = class(TSortableList)
  44.         function Compare(i1, i2 : Integer) : Integer; override;
  45.         end;
  46.  
  47.     2. Define the new compare method
  48.  
  49.       function Compare(i1, i2 : Integer) : Integer;
  50.         begin
  51.         case Key of
  52.           1 :
  53.             Result := AnsiCompareText(Copy(Strings[i1], 2, 254),
  54.                                       Copy(Strings[i2], 2, 254));
  55.           else
  56.             Result := inherited Compare(i1, i2);
  57.           end;
  58.         end;
  59.  
  60.     3. Specify the key you just defined.
  61.  
  62.       var
  63.         my_list : TExList;
  64.       begin
  65.       my_list := TExList.Create;
  66.       my_list.Key := 1;               { <<<<< New key is made active }
  67.       DoSomeStuff;
  68.       my_list.Free;
  69.       end;
  70.  
  71.       There you go!  I =strongly= suggest that any Key that you define Compare
  72.     methods for have a value of at least 1 and that all unhandled Key
  73.     values be passed to the inherited method, as above.  A Key of 0 is
  74.     defined to be the default alphabetical sort.
  75.  
  76.       Note that you can define a Key based on the objects in a list, like so:
  77.  
  78.       function Compare(i1, i2 : Integer) : Integer;
  79.         begin
  80.         case Key of
  81.           1 :
  82.             Result := AnsiCompareText(TSomeObject(Objects[i1]).Text,
  83.                                       TSomeObject(Objects[i2].Text);
  84.           else
  85.             Result := inherited Compare(i1, i2);
  86.           end;
  87.         end;
  88.  
  89.       Of course, it is your responsibility to be sure that the objects in
  90.     the list are the type that your Compare method assumes them to be.
  91.  
  92.                   === Important ===
  93.  
  94.    If you do have a list that is
  95.      a) sorted, and
  96.      b) determines its order via values derived from the objects that are
  97.         stored in the list,
  98.    watch out for changing objects in such a way as to change their sort order;
  99.    the TSortableList will not know that the list is now out of order and
  100.    calls to routines that depend on knowing this (such as Add) may fail to
  101.    work.  Your best bet in this case is to set Sorted to False, make whatever
  102.    changes to the objects you wish, and then set Sorted back to True.  This
  103.    will resort the list.
  104.  
  105.      I also took the liberty of "protecting" the Find method against the
  106.    possibility that someone would call it when the list was not sorted --
  107.    in that case, it now calls IndexOf (which, somewhat recursively, would
  108.    call Find if the list =was= sorted).  This is exactly what happened in
  109.    the example code for Find!  If you look at the method list for
  110.    TStringList, you won't find Find -- but you can do a topic search for
  111.    it.  The example code for Find works, but only by chance -- add another
  112.    string to either end of the list, and "Flowers" won't be found.  What's
  113.    wrong with the example code is that the list's Sorted property is not set
  114.    to True; the reason it (accidently) works is because the item that
  115.    was being found ("Flowers") happened to be the middle item in the list,
  116.    which is where the search algorithm looks first.
  117.  
  118.      If you have any comments, suggestions or even criticisms <g> for
  119.    TSortableList, hey, tough!  No, really, send me some mail at 71744,422.
  120.    I am particularly interested in bug reports.
  121.  
  122. *)
  123.  
  124. interface
  125.  
  126. Uses
  127.   Classes;
  128.  
  129. type
  130.   TSortableList = class(TStringList)
  131.     private
  132.     FOwnsObjects : Boolean;
  133.     FKey : Integer;
  134.     FAscending : Boolean;
  135.     procedure QuickSort(left, right: Integer);
  136.     function CallCompare(i1, i2 : Integer) : Integer;
  137.     procedure SetAscending(value : Boolean);
  138.     procedure SetKey(value : Integer);
  139.     protected
  140.     procedure PutObject(index : Integer; AObject : TObject); override;
  141.     function Compare(i1, i2 : Integer) : Integer; virtual;
  142.     public
  143.     constructor Create(owns_objects : Boolean);
  144.     procedure Clear; override;
  145.     procedure Delete(index : Integer); override;
  146.     function Find(const s : string; var index : Integer): Boolean; override;
  147.     procedure Sort; override;
  148.     property Ascending : Boolean read FAscending write SetAscending;
  149.     property Key : Integer read FKey write SetKey;
  150.     property OwnsObjects : Boolean read FOwnsObjects;
  151.     end;
  152.  
  153. implementation
  154.  
  155. Uses
  156.   SysUtils;
  157.  
  158. { Private Methods }
  159.  
  160. procedure TSortableList.QuickSort(left, right: Integer);
  161.   var
  162.     i, j, pivot : Integer;
  163.     s : String;
  164.   begin
  165.   i := left;
  166.   j := right;
  167.  
  168.   { Rather than store the pivot value (which was assumed to be a string),
  169.     store the pivot index }
  170.   pivot := (left + right) shr 1;
  171.  
  172.   repeat
  173.     while CallCompare(i, pivot) < 0 do
  174.       Inc(i);
  175.     while CallCompare(j, pivot) > 0 do
  176.       Dec(j);
  177.     if i <= j then
  178.       begin
  179.       Exchange(i, j);
  180.  
  181.       { If we just moved the pivot item, reset the pivot index }
  182.       if pivot = i then
  183.         pivot := j
  184.       else if pivot = j then
  185.         pivot := i;
  186.  
  187.       Inc(i);
  188.       Dec(j);
  189.       end;
  190.     until i > j;
  191.   if left < j then
  192.     QuickSort(left, j);
  193.   if i < right then
  194.     QuickSort(i, right);
  195.   end;
  196.  
  197. function TSortableList.CallCompare(i1, i2 : Integer) : Integer;
  198.   begin
  199.   Result := Compare(i1, i2);
  200.   if not FAscending then
  201.     Result := -Result;
  202.   end;
  203.  
  204. procedure TSortableList.SetAscending(value : Boolean);
  205.   begin
  206.   if value <> FAscending then
  207.     begin
  208.     FAscending := value;
  209.     if Sorted then
  210.       begin
  211.       Sorted := False;
  212.       Sorted := True;
  213.       end
  214.     end;
  215.   end;
  216.  
  217. procedure TSortableList.SetKey(value : Integer);
  218.   begin
  219.   if value <> FKey then
  220.     begin
  221.     FKey := value;
  222.     if Sorted then
  223.       begin
  224.       Sorted := False;
  225.       Sorted := True;
  226.       end
  227.     end;
  228.   end;
  229.  
  230. { Protected Methods }
  231.  
  232. function TSortableList.Compare(i1, i2 : Integer) : Integer;
  233.   begin
  234.   Result := AnsiCompareText(Strings[i1], Strings[i2]);
  235.   end;
  236.  
  237. { Public Methods }
  238.  
  239. constructor TSortableList.Create(owns_objects : Boolean);
  240.   begin
  241.   inherited Create;
  242.   FOwnsObjects := owns_objects;
  243.   FKey := 0;
  244.   FAscending := True;
  245.   end;
  246.  
  247. procedure TSortableList.Clear;
  248.   var
  249.     index : Integer;
  250.   begin
  251.   Changing;
  252.   if FOwnsObjects then
  253.     for index := 0 to Count - 1 do
  254.       GetObject(index).Free;
  255.   inherited Clear;
  256.   Changed;
  257.   end;
  258.  
  259. procedure TSortableList.Delete(index: Integer);
  260.   begin
  261.   Changing;
  262.   if FOwnsObjects then
  263.     GetObject(index).Free;
  264.   inherited Delete(index);
  265.   Changed;
  266.   end;
  267.  
  268. function TSortableList.Find(const s : string; var index : Integer): Boolean;
  269.   begin
  270.   if not Sorted then
  271.     begin
  272.     index := IndexOf(s);
  273.     Result := (index <> -1);
  274.     end
  275.   else
  276.     Result := inherited Find(s, index);
  277.   end;
  278.  
  279. procedure TSortableList.PutObject(index: Integer; AObject: TObject);
  280.   begin
  281.   Changing;
  282.   if FOwnsObjects then
  283.     GetObject(index).Free;
  284.   inherited PutObject(index, AObject);
  285.   Changed;
  286.   end;
  287.  
  288. procedure TSortableList.Sort;
  289.   begin
  290.   if not Sorted and (Count > 1) then
  291.     begin
  292.     Changing;
  293.     QuickSort(0, Count - 1);
  294.     Changed;
  295.     end;
  296.   end;
  297.  
  298. end.
  299.